//////////////////////////////////////////////////////////////////////////////////
//                                                                              //
//  This file is part of the buola project (https://code.google.com/p/buola/).  //
//                                                                              //
//  Copyright(c) 2007-2012 Xavi Gratal                                          //
//  gratal AT gmail DOT com                                                     //
//                                                                              //
//  Buola is free software: you can redistribute it and/or modify               //
//  it under the terms of the GNU General Public License as published by        //
//  the Free Software Foundation, either version 3 of the License, or           //
//  (at your option) any later version.                                         //
//                                                                              //
//  Buola is distributed in the hope that it will be useful,                    //
//  but WITHOUT ANY WARRANTY; without even the implied warranty of              //
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               //
//  GNU General Public License for more details.                                //
//                                                                              //
//  You should have received a copy of the GNU General Public License           //
//  along with buola.  If not, see <http://www.gnu.org/licenses/>.              //
//                                                                              //
//////////////////////////////////////////////////////////////////////////////////

#include <buola/buola.h>
#include <buola/io.h>

#include <iconv.h>
#include <cerrno>
#include <cwchar>
#include <buola/threads/thread.h>
#include <map>
#include <libintl.h>
#include <cstdarg>
#include <iostream>
#include <unicode/ustring.h>
#include <unicode/utf8.h>

namespace buola {

std::wstring decode(const char *pSrc,size_t pLen,const char *pCod)
{
    static std::map<const char*,iconv_t> sDecodeMap;
    static mutex sDecodeMutex;

    if(!pSrc) return std::wstring();

    wchar_t lBuffer[pLen+1];

    sDecodeMutex.lock();

    iconv_t lIConv;
    auto lConvIt=sDecodeMap.find(pCod);
    if(lConvIt==sDecodeMap.end())
    {
        lIConv=iconv_open("WCHAR_T",pCod);
        sDecodeMap[pCod]=lIConv;
    }
    else
    {
        lIConv=lConvIt->second;
        iconv(lIConv,nullptr,nullptr,nullptr,nullptr);
    }

    std::size_t lInC=pLen;
    std::size_t lOutC=pLen*sizeof(wchar_t);

    char *lInP=const_cast<char*>(pSrc);
    char *lOutP=(char*)lBuffer;

    if(iconv(lIConv,&lInP,&lInC,&lOutP,&lOutC)==(std::size_t)-1)
    {
        sDecodeMutex.unlock();
        if(!strcmp(pCod,COD_UTF8))
        {
            return decode(pSrc,COD_ISO8859_1);
        }
        else if(!strcmp(pCod,COD_ISO8859_1))
        {
            msg_warn() << "invalid encoding in decode\n";
            return decode(pSrc,COD_ASCII);
        }
        for(int i=0;i<=pLen;i++)
        {
            lBuffer[i]=(wchar_t)pSrc[i];
        }

        return lBuffer;
    }

    lBuffer[pLen-lOutC/4]=0;

    sDecodeMutex.unlock();
    return lBuffer;
}

std::wstring decode(const char *pSrc,const char *pCod)
{
    if(!pSrc) return std::wstring();

    std::size_t lLen;

    for(lLen=0;pSrc[lLen];lLen++) {}

    return decode(pSrc,lLen,pCod);
}

std::string encode(const wchar_t *pSrc,size_t pLen,const char *pCod)
{
    static std::map<const char*,iconv_t> sEncodeMap;
    static mutex sEncodeMutex;

    if(!pSrc) return std::string();
    
    int lBufferLen=pLen*6+1;
    char lBuffer[lBufferLen];

    unique_lock<mutex> lLock(sEncodeMutex);

    iconv_t lIConv;
    auto lConvIt=sEncodeMap.find(pCod);
    if(lConvIt==sEncodeMap.end())
    {
        lIConv=iconv_open(pCod,"WCHAR_T");
        sEncodeMap[pCod]=lIConv;
    }
    else
    {
        lIConv=lConvIt->second;
        iconv(lIConv,nullptr,nullptr,nullptr,nullptr);
    }

    std::size_t lInC=pLen*sizeof(wchar_t);
    std::size_t lOutC=lBufferLen;

    char *lInP=const_cast<char*>((const char*)pSrc);
    char *lOutP=(char*)lBuffer;

    if(iconv(lIConv,&lInP,&lInC,&lOutP,&lOutC)==(std::size_t)-1)
    {
        msg_info() << "can't encode!!!!!!!\n";
        back_trace();
        throw XInternal(I18N("can't do string encoding"));
    }

    lBuffer[lBufferLen-lOutC]=0;

    return lBuffer;
}

std::string encode(const wchar_t *pSrc,const char *pCod)
{
    if(!pSrc) return std::string();

    std::size_t lLen;

    for(lLen=0;pSrc[lLen];lLen++) {}

    return encode(pSrc,lLen,pCod);
}

std::string transcode(const char *pSrc,size_t pLen,const char *pFrom,const char *pTo)
{
    if(!pSrc) return std::string();

    int lBufferLen=pLen*6+1;
    char lBuffer[lBufferLen];

    iconv_t lIConv=iconv_open(pTo,pFrom);

    std::size_t lInC=pLen;
    std::size_t lOutC=lBufferLen;

    char *lInP=const_cast<char*>(pSrc);
    char *lOutP=(char*)lBuffer;

    if(iconv(lIConv,&lInP,&lInC,&lOutP,&lOutC)==(std::size_t)-1)
    {
        if(!strcmp(pFrom,COD_UTF8))
        {
            return transcode(pSrc,COD_ISO8859_1,pTo);
        }
        else if(!strcmp(pFrom,COD_ISO8859_1))
        {
            msg_warn() << "invalid encoding in transcode\n";
            return transcode(pSrc,COD_ASCII,pTo);
        }
        for(int i=0;i<=pLen;i++)
        {
            lBuffer[i]=(wchar_t)pSrc[i];
        }

        return lBuffer;
    }

    iconv_close(lIConv);

    lBuffer[lBufferLen-lOutC]=0;

    return lBuffer;
}

std::string transcode(const char *pSrc,const char *pFrom,const char *pTo)
{
    if(!pSrc) return std::string();

    std::size_t lLen;

    for(lLen=0;pSrc[lLen];lLen++) {}

    return transcode(pSrc,lLen,pFrom,pTo);
}

std::string I18N(const char *pStr)
{
    return gettext(pStr);
}

std::wstring WI18N(const char *pStr)
{
    return utf32(I18N(pStr));
}

static const char sHexDigits[16]={ '0','1','2','3','4','5','6','7',
                                    '8','9','a','b','c','d','e','f' };

std::string hex_string(const uint8_t *pData,size_t pLen)
{
    std::string lReturn;
    lReturn.resize(pLen*2);

    for(int i=0;i<pLen;i++)
    {
        lReturn[i*2]=sHexDigits[pData[i]>>4];
        lReturn[i*2+1]=sHexDigits[pData[i]&0x0f];
    }

    return lReturn;
    
}

std::wstring hex_wstring(const uint8_t *pData,size_t pLen)
{
    std::wstring lReturn;
    lReturn.resize(pLen*2);

    for(int i=0;i<pLen;i++)
    {
        lReturn[i*2]=sHexDigits[pData[i]>>4];
        lReturn[i*2+1]=sHexDigits[pData[i]&0x0f];
    }

    return lReturn;
}

std::string str_printf(const char *pFormat,...)
{
    std::string lResult(128,'0');

    va_list lVA;
    int lLength;

    while(true)
    {
        va_start(lVA,pFormat);
        lLength=vsnprintf(&lResult[0],lResult.size(),pFormat,lVA);
        va_end(lVA);

        if(lLength<0)
            throw XInvalid("error in str_printf");
        
        if(lLength<lResult.size()) break;
        
        lResult.resize(lLength+1);
    }
    
    lResult.resize(lLength);
    return lResult;
}

std::string str_printf(const std::string &pFormat,...)
{
    std::string lResult(128,'0');

    va_list lVA;
    int lLength;

    while(true)
    {
        va_start(lVA,pFormat);
        lLength=vsnprintf(&lResult[0],lResult.size(),pFormat.c_str(),lVA);
        va_end(lVA);

        if(lLength<0)
            throw XInvalid("error in str_printf");
        
        if(lLength<lResult.size()) break;
        
        lResult.resize(lLength+1);
    }
    
    lResult.resize(lLength);
    return lResult;
}

std::wstring wstr_printf(const wchar_t *pFormat,...)
{
    std::wstring lResult(128,L'0');

    va_list lVA;
    int lLength;

    while(true)
    {
        va_start(lVA,pFormat);
        lLength=vswprintf(&lResult[0],lResult.size(),pFormat,lVA);
        va_end(lVA);

        if(lLength<0)
            throw XInvalid("error in str_printf");
        
        if(lLength<lResult.size()) break;
        
        lResult.resize(lLength+1);
    }
    
    lResult.resize(lLength);
    return lResult;
}

std::wstring wstr_printf(const std::wstring &pFormat,...)
{
    std::wstring lResult(128,L'0');

    va_list lVA;
    int lLength;

    while(true)
    {
        va_start(lVA,pFormat);
        lLength=vswprintf(&lResult[0],lResult.size(),pFormat.c_str(),lVA);
        va_end(lVA);

        if(lLength<0)
            throw XInvalid("error in str_printf");
        
        if(lLength<lResult.size()) break;
        
        lResult.resize(lLength+1);
    }
    
    lResult.resize(lLength);
    return lResult;
}

std::wstring utf32(const char16_t *pSrc, size_t pLen)
{
    wchar_t *lBuffer=alloc_array<wchar_t>(pLen+1);
    int32_t lLength;
    UErrorCode lErrorCode=U_ZERO_ERROR;
    u_strToUTF32(reinterpret_cast<UChar32*>(lBuffer),pLen+1,&lLength,pSrc,pLen,&lErrorCode);
    if(U_FAILURE(lErrorCode))
        throw XInvalid("invalid UTF16 sequence");
    return {lBuffer,(size_t)lLength};
}

std::wstring utf32(const char16_t *pSrc)
{
    return utf32(pSrc,u_strlen(pSrc));
}

std::wstring utf32(const char *pSrc, size_t pLen)
{
    wchar_t *lBuffer=alloc_array<wchar_t>(pLen);
    int32_t lOff=0;
    size_t lLen=0;
    while(lOff<pLen)
    {
        UChar32 lPoint;
        U8_NEXT(pSrc,lOff,pLen,lPoint);
        if(lPoint<0)
            throw XInvalid("invalid UTF8 sequence");
        lBuffer[lLen++]=lPoint;
    }
    
    return {lBuffer,lLen};
}

std::wstring utf32(const char *pSrc)
{
    return utf32(pSrc,strlen(pSrc));
}

std::u16string utf16(const char *pSrc, size_t pLen)
{
    char16_t *lBuffer=alloc_array<char16_t>(pLen+1);
    int32_t lLength;
    UErrorCode lErrorCode=U_ZERO_ERROR;
    u_strFromUTF8(lBuffer,pLen+1,&lLength,pSrc,pLen,&lErrorCode);
    if(U_FAILURE(lErrorCode))
        throw XInvalid("invalid UTF8 sequence");
    return {lBuffer,(size_t)lLength};
}

std::u16string utf16(const char *pSrc)
{
    return utf16(pSrc,strlen(pSrc));
}

std::u16string utf16(const wchar_t *pSrc, size_t pLen)
{
    char16_t *lBuffer=alloc_array<char16_t>(pLen*2+1);
    int32_t lLength;
    UErrorCode lErrorCode=U_ZERO_ERROR;
    u_strFromUTF32(lBuffer,pLen*2+1,&lLength,reinterpret_cast<const UChar32*>(pSrc),pLen,&lErrorCode);
    if(U_FAILURE(lErrorCode))
        throw XInvalid("invalid UTF32 sequence");
    return {lBuffer,(size_t)lLength};
}

std::u16string utf16(const wchar_t *pSrc)
{
    return utf16(pSrc,wcslen(pSrc));
}

std::string utf8(const char16_t *pSrc, size_t pLen)
{
    char *lBuffer=alloc_array<char>(pLen*4+1);
    int32_t lLength;
    UErrorCode lErrorCode=U_ZERO_ERROR;
    u_strToUTF8(lBuffer,pLen*4+1,&lLength,pSrc,pLen,&lErrorCode);
    if(U_FAILURE(lErrorCode))
        throw XInvalid("invalid UTF16 sequence");
    return {lBuffer,(size_t)lLength};
}

std::string utf8(const char16_t *pSrc)
{
    return utf8(pSrc,u_strlen(pSrc));
}

std::string utf8(const wchar_t *pSrc, size_t pLen)
{
    char *lBuffer=alloc_array<char>(pLen*4);
    size_t lLen=0;
    for(int i=0;i<pLen;i++)
        U8_APPEND_UNSAFE(lBuffer,lLen,pSrc[i]);
    return {lBuffer,lLen};
}

std::string utf8(const wchar_t *pSrc)
{
    return utf8(pSrc,wcslen(pSrc));
}

/*namespace buola*/ }
